package com.atguigu.gmall.order.controller;

import com.atguigu.gmall.cart.client.CartFeignClient;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.util.AuthContextHolder;
import com.atguigu.gmall.model.cart.CartInfo;
import com.atguigu.gmall.model.order.OrderDetail;
import com.atguigu.gmall.model.order.OrderInfo;
import com.atguigu.gmall.order.service.OrderService;
import com.atguigu.gmall.product.client.ProductFeignClient;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * @Author atguigu-mqx
 * @Description TODO
 * @Date 2023/7/22 14:37
 * @Version 1.0
 */
@RestController
@RequestMapping("/api/order")
public class OrderApiController {

    @Autowired
    private CartFeignClient cartFeignClient;

    @Autowired
    private OrderService orderService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private ProductFeignClient productFeignClient;

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;
    /**
     * 获取订单结算页数据
     * @param request
     * @return
     */
    @GetMapping("/auth/trade")
    public Result authTrade(HttpServletRequest request){
        //  获取用户Id
        String userId = AuthContextHolder.getUserId(request);
        //  创建一个Map
        HashMap<String, Object> map = new HashMap<>();
        //  获取购物车中的选中商品列表.
        List<CartInfo> cartCheckedList = cartFeignClient.getCartCheckedList(userId);
        //  原子操作类.
        AtomicInteger totalNum = new AtomicInteger();
        //  需要将 CartInfo ---> OrderInfo;
        List<OrderDetail> detailArrayList = cartCheckedList.stream().map(cartInfo -> {
            //  声明一个订单明细对象
            OrderDetail orderDetail = new OrderDetail();
            orderDetail.setSkuId(cartInfo.getSkuId());
            orderDetail.setSkuName(cartInfo.getSkuName());
            orderDetail.setImgUrl(cartInfo.getImgUrl());
            orderDetail.setOrderPrice(cartInfo.getSkuPrice());
            orderDetail.setSkuNum(cartInfo.getSkuNum());
            //  总数量
            totalNum.addAndGet(orderDetail.getSkuNum());
            return orderDetail;
        }).collect(Collectors.toList());
        //  计算总价格：
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setOrderDetailList(detailArrayList);
        orderInfo.sumTotalAmount();
        //  送货清单：
        map.put("detailArrayList",detailArrayList);
        map.put("totalNum",totalNum);
        map.put("totalAmount",orderInfo.getTotalAmount());
        //  存储流水号.
        map.put("tradeNo",orderService.getTradeNo(userId));
        //  返回map集合
        return Result.ok(map);
    }

    //  http://api.gmall.com/api/order/auth/submitOrder?tradeNo=null tradeNo：流水号，校验订单的。

    /**
     * 提交订单。
     * @param orderInfo
     * @return
     */
    @PostMapping("/auth/submitOrder")
    public Result submitOrder(@RequestBody OrderInfo orderInfo,HttpServletRequest request){
        //  获取用户Id
        String userId = AuthContextHolder.getUserId(request);
        orderInfo.setUserId(Long.parseLong(userId));

        //  在保存订单之前，进行比较.
        String tradeNo = request.getParameter("tradeNo");
        //  调用比较方法
        //        Boolean result = this.orderService.checkTradeNo(tradeNo, userId);
        //        if (!result){
        //            return Result.fail().message("不能重复提交订单");
        //        }
        //        //  删除缓存的流水号.
        //        this.orderService.delTradeNo(userId);
        //  这方式删除流水号是否安全? [多线程操作] 如果不安全，可以使用什么方案解决? 使用lua脚本解决.

        //  分布式锁：使用lua 脚本能够保证删除具有原子性！
        String scriptText = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                "then\n" +
                "    return redis.call(\"del\",KEYS[1])\n" +
                "else\n" +
                "    return 0\n" +
                "end";
        //  使用 redisson 底层调用 使用分布式锁的时候， 相当于使用hash 命令做的分布式锁！
        //  使用redis 调用 方法，去执行lua脚本.
        //  第一个参数封装lua脚本对象
        DefaultRedisScript<Long> defaultRedisScript = new DefaultRedisScript<>();
        defaultRedisScript.setScriptText(scriptText);
        defaultRedisScript.setResultType(Long.class);
        //  第二个参数：键 key  set key=tradeNoKey value=String tradeNo = UUID.randomUUID().toString();
        String tradeNoKey = userId+":tradeNo";
        //  第三个参数：值 value
        //  拿页面的tradeNo 与 缓存的 tradeNo 比较！
        Long result = (Long) this.redisTemplate.execute(defaultRedisScript, Arrays.asList(tradeNoKey), tradeNo);
        //  判断
        if (result==0l){
            //  不能重复提交订单
            return Result.fail().message("不能重复提交订单");
        }
        //  判断是否有足够的库存！
        List<OrderDetail> orderDetailList = orderInfo.getOrderDetailList();
        //  jdk1.8 提供的多线程类
        ArrayList<CompletableFuture> completableFutureArrayList = new ArrayList<>();
        //  存储错误信息的集合.
        ArrayList<String> errorList = new ArrayList<>();

        //  判断
        if (!CollectionUtils.isEmpty(orderDetailList)){
            for (OrderDetail orderDetail : orderDetailList) {
                CompletableFuture<Void> stockCompletableFuture = CompletableFuture.runAsync(() -> {
                    //  skuId, skuNum
                    Boolean exist = this.orderService.checkStock(orderDetail.getSkuId(), orderDetail.getSkuNum());
                    //  判断 exist = true; 有库存，false; 没有库存.
                    if (!exist) {
                        errorList.add(orderDetail.getSkuId() + "库存不足，请重新下单.");
                    }
                },threadPoolExecutor);
                //  将验证库存的线程添加到集合中。
                completableFutureArrayList.add(stockCompletableFuture);

                CompletableFuture<Void> priceCompletableFuture = CompletableFuture.runAsync(() -> {
                    //  需要校验商品的价格：
                    //  实时价格：
                    BigDecimal skuPrice = this.productFeignClient.getSkuPrice(orderDetail.getSkuId());
                    //  商品订单价格：
                    BigDecimal orderPrice = orderDetail.getOrderPrice();
                    //  比价：
                    //  skuPrice.compareTo(orderPrice)!=0 价格有变动  skuPrice.compareTo(orderPrice) == 1 涨价 -1降价
                    if (skuPrice.compareTo(orderPrice) != 0) {
                        //  信息提示涨价，降价
                        String msg = skuPrice.compareTo(orderPrice) == 1 ? "涨价" : "降价";
                        //  提示价格变动金额
                        BigDecimal price = skuPrice.subtract(orderPrice).abs();
                        //  价格有变动了，则需要系统自动更新购物车的价格!
                        //  hget key field; hset key field value;
                        //  购物车key
                        String cartKey = RedisConst.USER_KEY_PREFIX + userId + RedisConst.USER_CART_KEY_SUFFIX;
                        CartInfo cartInfo = (CartInfo) this.redisTemplate.opsForHash().get(cartKey, orderDetail.getSkuId().toString());
                        //  判断
                        if (null != cartInfo) {
                            cartInfo.setSkuPrice(skuPrice);
                            cartInfo.setUpdateTime(new Date());
                            //  写回最新数据
                            this.redisTemplate.opsForHash().put(cartKey, orderDetail.getSkuId().toString(), cartInfo);
                        }
                        //  有信息提示.
                        errorList.add(orderDetail.getSkuId() + "价格有变动:" + msg + "￥" + price);
                    }
                },threadPoolExecutor);
                //  添加价格的线程
                completableFutureArrayList.add(priceCompletableFuture);
            }
        }
        //  多任务组合： 集合变数组.
        CompletableFuture.allOf(completableFutureArrayList.toArray(new CompletableFuture[completableFutureArrayList.size()])).join();
        //  判断是否有错误
        if (errorList.size()>0){
            //  给用户展示都有哪些错误. xxx,xxxprice,xxx;
            return Result.fail().message(StringUtils.join(errorList,","));
        }
        //  调用服务层方法.
        Long orderId = this.orderService.saveOrderInfo(orderInfo);
        //  返回订单
        return Result.ok(orderId);
    }

    //  ?order_status=
    @GetMapping("/auth/{page}/{limit}")
    public Result getOrder(@PathVariable Long page,
                           @PathVariable Long limit,
                           HttpServletRequest request){
        //  获取用户Id
        String userId = AuthContextHolder.getUserId(request);
        //  获取查询状态
        String orderStatus = request.getParameter("orderStatus");
        //  创建Page对象
        Page<OrderInfo> orderInfoPage = new Page<>(page,limit);
        //  调用服务层方法.
        IPage<OrderInfo> infoIPage = this.orderService.getOrder(orderInfoPage,userId,orderStatus);
        //  返回数据。
        return Result.ok(infoIPage);
    }

    /**
     * 根据订单Id 获取订单对象
     * @param orderId
     * @return
     */
    @GetMapping("/inner/getOrderInfo/{orderId}")
    public OrderInfo getOrderInfo(@PathVariable Long orderId){
        //  如果只想展示支付页面信息，这个方法没有问题！
        //  但是这个方法，后续我们还需要获取到订单明细，不行! 因为还需要订单详情！
        return this.orderService.getOrderInfo(orderId);
    }

    /**
     * 拆单接口
     * @param request
     * @return
     */
    @PostMapping("/orderSplit")
    public List<Map<String,Object>> orderSplit(HttpServletRequest request){
        //  获取订单Id
        String orderId = request.getParameter("orderId");
        String wareSkuMap = request.getParameter("wareSkuMap");
        //  调用服务层拆单方法
        List<OrderInfo> orderInfoList = this.orderService.orderSplit(orderId,wareSkuMap);
        //  判断
        if (!CollectionUtils.isEmpty(orderInfoList)){
            //  循环遍历
            List<Map<String, Object>> mapList = orderInfoList.stream().map(orderInfo -> this.orderService.initWareJson(orderInfo)).collect(Collectors.toList());
            return mapList;
        }
        //  默认返回
        return null;
    }

    /**
     * 保存秒杀订单数据.
     * @param orderInfo
     * @return
     */
    @PostMapping("/inner/seckill/submitOrder")
    public Long submitOrder(@RequestBody OrderInfo orderInfo){
        //  调用服务层方法.
        Long orderId = this.orderService.saveOrderInfo(orderInfo);
        return orderId;
    }

}
